Linux C 程序入口函数 main() 小解
2019年10月27日
Linux 系统中,C 程序总是从 main()
函数开始的,当用户编写好的程序在运行的时候,操作系统慧使用 exec()
函数运行程序。在调用 main()
之前,exec()
系统调用会先调用一个特殊的启动例程,负责从操作系统内核读取程序的命令行参数,为 main()
函数准备好工作环境。
main()
函数的定义为
c
int main(int argc, char* argv[], char* envp[]);
参数 argc 表示 命令行参数的个数,即参数 argv 的长度,argv 是一个字符串数组指针,argv 的元素就是参数的内容。envp 是系统环境变量列表,存放了一组进程运行过程中会用到的环境变量。
ANSI 规定 main()
函数只有前两个参数,而 POSIX 则使用了一个全局的环境变量 environ 来取代 envp ,应用程序可以通过 getenv()
和 putenv()
函数来读取或者设置一个环境变量。
系统如何执行 main()
函数
用 strace 来分析 main()
的执行。我们先写一段测试用的程序:
c
#include <stdio.h>
int main(int argc, char* argv[], char* envp[]) {
int i = 0;
for (i = 0; i < argc; i++) {
printf("Argument %d is %s.\n", i, argv[i]);
}
return 0;
}
接下来编译链接:
bash
$ gcc -o main mian.c
得到可执行文件 main ,然后运行它
bash
$ ./main 'arg1' "arg2" arg3
Argument 0 is ./main.
Argument 1 is arg1.
Argument 2 is arg2.
Argument 3 is arg3.
程序运行正确。接下来用 strace 查看系统调用情况:
bash
$ strace ./main 'arg1' "arg2" arg3
execve("./main", ["./main", "arg1", "arg2", "arg3"], 0x7fd6828c78 /* 32 vars */) = 0
brk(NULL) = 0x558ddaf000
faccessat(AT_FDCWD, "/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
faccessat(AT_FDCWD, "/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=89739, ...}) = 0
mmap(NULL, 89739, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f81dba000
close(3) = 0
faccessat(AT_FDCWD, "/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0 \10\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1341080, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f81df8000
mmap(NULL, 1409880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f81c61000
mprotect(0x7f81da1000, 61440, PROT_NONE) = 0
mmap(0x7f81db0000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x13f000) = 0x7f81db0000
mmap(0x7f81db6000, 13144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f81db6000
close(3) = 0
mprotect(0x7f81db0000, 16384, PROT_READ) = 0
mprotect(0x5585a3f000, 4096, PROT_READ) = 0
mprotect(0x7f81dfc000, 4096, PROT_READ) = 0
munmap(0x7f81dba000, 89739) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
brk(NULL) = 0x558ddaf000
brk(0x558ddd0000) = 0x558ddd0000
write(1, "Argument 0 is ./main.\n", 22Argument 0 is ./main.
) = 22
write(1, "Argument 1 is arg1.\n", 20Argument 1 is arg1.
) = 20
write(1, "Argument 2 is arg2.\n", 20Argument 2 is arg2.
) = 20
write(1, "Argument 3 is arg3.\n", 20Argument 3 is arg3.
) = 20
exit_group(0) = ?
+++ exited with 0 +++
运行程序,系统将会从 mian()
函数开始执行,执行系统调用 execve()
函数为程序开辟新的进程。执行 execve()
函数的时候,系统将执行 ./main
命令中的所有参数以字符串指针的方式传递给了 execve()
。执行参数包含可执行文件自身,所以我们可以看到 argv 的长度为 4。
了解系统调用 execve()
函数可以查看这篇了解
最后更新时间: 2025年09月02日